//============================================================================
// Name        : usbi2cd.cpp
// Author      : Victor Gaspar
// Version     :
// Copyright   : 
// Description : Daemon Controller for Bus Adaptor USBI2C
//============================================================================

/***********/
/* For IPC */
/***********/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>

#define MSGSZ     128

/* Declare the message structure. */
typedef struct {
	long mtype;
	char mtext[MSGSZ];
} message_buf;

int msqidCommandQueue;
int msqidResponseQueue;

/**********************/
/* For Adaptor USBI2C */
/**********************/
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <stdlib.h>

#define P_ERROR	-1		// to check for errors when dealing with the port

struct termios options;

int fd; // File Descriptor for openPort

const char *DELIMITER = ";";
#define MAXCOMMANDS   10
#define MAXCOMMANDLENGTH 4
#define MAXMESSAGELENGTH (MAXCOMMANDS*(MAXCOMMANDLENGTH+1)-1)

void closePort(void);
void closeQueues(void);

/*******************/
/* For daemon code */
/*******************/
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>

#define RUNNING_DIR	"/tmp"
#define LOCK_FILE	"usbi2cd.lock"
#define LOG_FILE	"usbi2cd.log"

void log_message(char* filename, char* message)
{
	FILE *logfile;
	logfile = fopen(filename, "a");
	if (!logfile)
		return;
	fprintf(logfile, "%s\n", message);
	fclose(logfile);
}

void signal_handler(int sig)
{
	switch (sig) {
	case SIGHUP:
		closePort();
		closeQueues();
		remove(LOCK_FILE);
		log_message(LOG_FILE, "LOG: Hangup signal catched");
		break;
	case SIGTERM:
		closePort();
		closeQueues();
		remove(LOCK_FILE);
		log_message(LOG_FILE, "LOG: Terminate signal catched");
		exit(0);
		break;
	}
}

void daemonize() {
	int i, lfp;
	char str[10];
	if (getppid() == 1)
		return; /* already a daemon */

	i = fork();
	if (i < 0)
		exit(1); /* fork error */
	if (i > 0)
		exit(0); /* parent exits */

	/* child (daemon) continues */

	setsid(); /* obtain a new process group */

	for (i = getdtablesize(); i >= 0; --i)
		close(i); /* close all descriptors */

	i = open("/dev/null", O_RDWR);
	dup(i);
	dup(i); /* handle standart I/O */

	umask(027); /* set newly created file permissions */

	chdir(RUNNING_DIR); /* change running directory */

	lfp = open(LOCK_FILE, O_RDWR | O_CREAT, 0640);
	if (lfp < 0)
		exit(1); /* can not open */
	if (lockf(lfp, F_TLOCK, 0) < 0)
		exit(0); /* can not lock */

	/* first instance continues */
	sprintf(str, "%d\n", getpid());
	write(lfp, str, strlen(str)); /* record pid to lockfile */
	signal(SIGCHLD, SIG_IGN); /* ignore child */
	signal(SIGTSTP, SIG_IGN); /* ignore tty signals */
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGHUP, signal_handler); /* catch hangup signal */
	signal(SIGTERM, signal_handler); /* catch kill signal */
}

int openPort(void) {
	int fdl; // File descriptor for the port

	fdl = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY); // Open port for read and write not making it a controlling terminal
	if (fdl == P_ERROR) {
		log_message(LOG_FILE, "ERROR opening port");
	} else {
		fcntl(fdl, F_SETFL, 0); // Get the current options for the port
		tcgetattr(fdl, &options);
		cfsetispeed(&options, B19200); // Set the baud rates to 19200
		options.c_cflag |= (CLOCAL | CREAD); // Enable the receiver and set local mode
		options.c_cflag &= ~PARENB; // No parity bit
		options.c_cflag &= ~CSTOPB; // Set 2 stop bits
		options.c_cflag &= ~CSIZE; // Set the character size
		options.c_cflag |= CS8;
		tcsetattr(fdl, TCSANOW, &options); // Set the new options for the port
	}
	return (fdl);
}

void closePort() {
	if (close(fd) == P_ERROR) // Close the port if it returns an error then display an error report
	{
		log_message(LOG_FILE, "ERROR while trying to close port");
	}
}

void closeQueues(){
	msgctl(msqidCommandQueue, IPC_RMID, NULL);
	msgctl(msqidResponseQueue, IPC_RMID, NULL);
}

int writeData(int fd, int nbytes, unsigned char* sbuf) {
	int bytes;

	bytes = write(fd, sbuf, nbytes); // Write nbytes of data from wbuf
	if (bytes == P_ERROR) // If write returns an error (-1)
	{
		log_message(LOG_FILE, "ERROR while trying to write data");
		return -1;
	} else if (bytes != nbytes) {
		size_t len;
		char *string;
		len = (size_t)snprintf(NULL, 0, "WARNING: Only %u bytes written out of %u requested\n", bytes, nbytes);
		string = malloc(len);
		snprintf(string, len, "WARNING: Only %u bytes written out of %u requested\n", bytes, nbytes);
		log_message(LOG_FILE, string);
		free(string);
		return -1;
	}
	return 0;
}

int readData(int fd, int nbytes, unsigned char* sbuf) {
	int bytes;

	bytes = read(fd, sbuf, nbytes); // Read nbytes of data into rbuf
	if (bytes == P_ERROR) // If read returns and error (-1)
	{
		log_message(LOG_FILE, "ERROR while trying to read data");
		return -1;
	} else if (bytes != nbytes) {
		size_t len;
		char *string;
		len = (size_t)snprintf(NULL, 0, "WARNING: Only %u bytes read out of %u requested\n\n", bytes, nbytes);
		string = malloc(len);
		snprintf(string, len, "WARNING: Only %u bytes read out of %u requested\n\n", bytes, nbytes);
		log_message(LOG_FILE, string);
		free(string);
		return -1;
	} else if (sbuf[0] == '0'){
		log_message(LOG_FILE, "Command not executed!");
		return -1;
	}

	return 0;
}

int main() {
	daemonize();

	log_message(LOG_FILE, "LOG: USBI2C daemon starting...");

	unsigned char sbuf[10]; // Stores data to be read and written
	key_t keyCommandQueue;
	key_t keyResponseQueue;
	message_buf rbuf;
	int msgflg = IPC_CREAT | 0666;

	/*
	 * Get the message queue id for the queue name 1234.
	 * If it does not exists, create it
	 */
	keyCommandQueue = 1234;
	keyResponseQueue = 4321;

	if ((msqidCommandQueue = msgget(keyCommandQueue, msgflg)) < 0) {
		log_message(LOG_FILE, "ERROR opening Command queue");
		exit(1);
	}

	if ((msqidResponseQueue = msgget(keyResponseQueue, msgflg)) < 0) {
		log_message(LOG_FILE, "ERROR opening Response queue");
		exit(1);
	}

	fd = openPort(); // Open port to USBi2C
	if (fd > P_ERROR)
	{
		int bdata; // Number of data bytes received
		log_message(LOG_FILE, "LOG: USBI2C daemon started...");

		/* Receive a MESSAGE (type 1) from the IPC COMMAND QUEUE */
		while (1) {
			bdata = msgrcv(msqidCommandQueue, &rbuf, MSGSZ, 1, 0);
			if (bdata > 1) {
				/* Log the message. */
				log_message(LOG_FILE, rbuf.mtext);

				// Process rbuf.mtext (MESSAGE) to get the COMMANDs
				// A MESSAGE could be "0x55;0xB0;0x00;0x04;0x04;0x05"

				if (strlen(rbuf.mtext) > MAXMESSAGELENGTH + 1)
					return -1;

				int CommandNumber = 0;
				char *token = strtok(rbuf.mtext, DELIMITER);
				char *end;

				while (token != NULL
						&& CommandNumber < MAXCOMMANDS
						&& strlen(token) < MAXCOMMANDLENGTH+1) {
					//strcpy(commands[CommandNumber], token);
					sbuf[CommandNumber] = strtol(token, &end, 16);
					token = strtok(NULL, DELIMITER);
					++CommandNumber;
				}

				// Send COMMANDS to the USBI2C port
				//TODO: CREO QUE EL PUERTO SE CIERRA DESPUÉS DE UN RATO SIN USARLO
				if(writeData(fd, CommandNumber, sbuf) < 0){
					// Send the USBI2C port's RESPONSE to the IPC RESPONSE QUEUE
					message_buf resp;
					size_t resplength;
					strcpy(resp.mtext, "ERROR writing data!!"); // SOLO PARA TESTEAR!!!!!!!
					resp.mtype = 1;
					resplength = strlen(resp.mtext) + 1;

					if (msgsnd(msqidResponseQueue, &resp, resplength, IPC_NOWAIT) < 0) {
						log_message(LOG_FILE, "ERROR sending response to the Response queue");
						exit(1);
					}
				} else {
					// Send the USBI2C port's RESPONSE to the IPC RESPONSE QUEUE
					message_buf resp;
					size_t resplength;

					if (readData(fd, 1, sbuf)< 0){
						log_message(LOG_FILE, "ERROR");
						strcpy(resp.mtext, "ERROR"); // SOLO PARA TESTEAR!!!!!!!
					} else {
						log_message(LOG_FILE, "OK");
						strcpy(resp.mtext, "OK"); // SOLO PARA TESTEAR!!!!!!!
					}

					resp.mtype = 1;
					resplength = strlen(resp.mtext) + 1;

					if (msgsnd(msqidResponseQueue, &resp, resplength, IPC_NOWAIT) < 0) {
						log_message(LOG_FILE, "ERROR sending response to the Response queue");
						exit(1);
					}
				}
			} else if (bdata <= 1){
				log_message(LOG_FILE, "ERROR null command received");
				message_buf resp;
				size_t resplength;
				strcpy(resp.mtext, "ERROR null command received");
				resp.mtype = 1;
				resplength = strlen(resp.mtext) + 1;

				if (msgsnd(msqidResponseQueue, &resp, resplength, IPC_NOWAIT) < 0) {
					log_message(LOG_FILE, "ERROR sending response to the Response queue");
					exit(1);
				}
			} else {
				log_message(LOG_FILE, "ERROR receiving message from the Command queue");
				exit(1);
			}
		}
	}
	else // It was not possible to open usbi2c port
		exit(1);

	exit(0);
}

